/**
 * 
 */
package it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine;

import java.util.HashMap;
import java.util.List;


import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.AbilityAttackAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.GenericLogicAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionCoordinateInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionMediator;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionSpellInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.RemoveStartingPointAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.SelectCharacterAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.AddCharacterInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.CharacterAttackAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.EndTurnAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.GenericActionInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.MovementAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.StartingPointAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.Character;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.items.ItemFactory;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.Stats;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.StatsFactory;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.BattlePlayer;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.SimplePlayer;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Chessboard;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Map;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.EndMatchEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.EventManager;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.TurnEvent;

/**
 * @author Massimo Canonaco
 */
public class MatchManager 
{

	/** */
	static public final int MAX_CHARACTERS_PER_PLAYER = 3;
	
	/** */
	static public final int MINIMUM_CHARACTERS_PER_PLAYER = 1;
	
	/**Index of the 'client' player.*/
	static public final int CLIENT = 0;
	
	/**Index of the opposite/enemy player.*/
	static public final int ENEMY = 1;
	
	/**It's the experience for the characters who played the match and are owned by the player who lost the match.*/ //TODO: SET LOSER EXP
	static private final int LOSER_EXPERIENCE = 80;
	
	/**It's the experience for the characters who played the match and are owned by the player who won the match.*/ //TODO: SET winner EXP
	static private final int WINNER_EXPERIENCE = 200;
	
	/**It's the starting value of gold to set for each turn.*/
	private final int STARTING_SKILL_POINTS;
	
	/** */
	private final int MAX_TURN_TIME = 120000; // 2 minuti
	
	/**Index of the current player. */
	private int current;
	
	/**At end of turn this the value to add to the skill points of the player who has just finished the turn. */
	private final float SKILL_POINTS_UPDATE;
	
	/**Map of the match. */
	private Map map;

	/**Array of players. */
	private BattlePlayer[] players;
	
	/**Status of the game. */
	private boolean gameStatus;
	
	/**It's the maximum value for the battle characters which player can use. */
	private final int MAX_CHARACTER;
	
	/**It's the time when the turn starts. */
	protected long startSkip;
	
	/**It's the time when the turn ends. */
	protected long tempSkip;
	
	/**It's the bonus skill points at every end turn. It's calculates by the <i>startSkip</i>, the <i>tempSkip</i> and the <i>MAX_TURN_TIME</i>.*/
	private float bonusSkillPoints;
	
	/**It's the turn event for the next turn. */
	private TurnEvent turnEvent;
	
	/** */
	private float currentTimeLeft;
	
	/**If turn has just been ended but there's an action in progress, there's a little overtime until action is not finished. This "possibility" is handled by this variable. </br>
	 * <b>True</b> means over time </br>
	 * <b>False</b> means NO over time. */
	private boolean overTimeTurn;
	
	/** It's the factory for rebuilding the characters' statics when match ends. */
	private StatsFactory statsFactory;
	
	/** */
	public MatchManager(int startingSkillPoints,int skillPoints, int max, final StatsFactory factory)
	{
		players = new BattlePlayer[2];
		SKILL_POINTS_UPDATE = skillPoints;
		STARTING_SKILL_POINTS = startingSkillPoints;
		MAX_CHARACTER = max;
		current = CLIENT;
		gameStatus = false;
		bonusSkillPoints = 0;
		turnEvent = new TurnEvent();
		currentTimeLeft = MAX_TURN_TIME;
		overTimeTurn = false;
		statsFactory = factory;
	}
	
	/** */
	public MatchManager(final MatchManager matchManager)
	{
		players = new BattlePlayer[2];
		SKILL_POINTS_UPDATE = matchManager.SKILL_POINTS_UPDATE;
		STARTING_SKILL_POINTS = matchManager.STARTING_SKILL_POINTS;
		MAX_CHARACTER = matchManager.MAX_CHARACTER;
		current = CLIENT;
		gameStatus = false;
		bonusSkillPoints = 0;
		turnEvent = new TurnEvent();
		currentTimeLeft = MAX_TURN_TIME;
		overTimeTurn = false;
		statsFactory = matchManager.statsFactory;
	}
	
	/** */
	public BattlePlayer getCurrentPlayer()
	{
		return players[current];
	}
	
	/** */
	public BattlePlayer getClientPlayer()
	{
		return players[CLIENT];
	}
	
	/** */
	public BattlePlayer getEnemy()
	{
		return players[ENEMY];
	}
	
	/** */
	public void nextTurn()
	{
		overTimeTurn = false;
		
		// Get the time when this function is called.
		tempSkip = System.currentTimeMillis();
		
		// Get the bonus skill points.
		bonusSkillPoints = (tempSkip - startSkip)/MAX_TURN_TIME;
		
		// If the bonus skill points is a negative value, set it to zero; let his value otherwise.
		bonusSkillPoints = bonusSkillPoints >= 0 ? bonusSkillPoints : 0;
		
		// Update the current player, the player who's ending the turn.
		players[current].getActions().setNoAction(); 
//		players[current].update(SKILL_POINTS_UPDATE+bonusSkillPoints);
		players[current].update(SKILL_POINTS_UPDATE); // NO MORE BONUS!
		
		// Set the player's nickname to the old one.
		turnEvent.setOldPlayer(players[current].getNickname());
		
		// Get the next player;
		current = (current+1)%players.length;
		
		// Reset bonusSkillPoints and tempSkip, and update the startSkip.
		bonusSkillPoints = 0;
		startSkip = tempSkip;
		tempSkip = 0;
		
		// Set the player's nickname to the new one and set success to true.
		turnEvent.setNewPlayer(players[current].getNickname());
		turnEvent.setSuccess(true);
		
		
		for(Character character : players[current].getCharacters())
			character.setSpellCastable(true);
		
		overTimeTurn = false;
		currentTimeLeft = MAX_TURN_TIME;
		

		players[current].getActions().setNoAction(); 
//		Time t = new Time(tempSkip);
//		System.out.println("NEXT TURN!!! ");
//		System.out.println(turnEvent.getNewPlayer());

		// Throw the event.
		EventManager.getInstance().triggerEvent(turnEvent);
	}
	
	/**
	 * @return the max character
	 */
	public int getMaxCharacter() {
		return MAX_CHARACTER;
	}
	
	/** */
	public void update()
	{
		if(!overTimeTurn)
		{
			skipTime();
		}

		if( currentTimeLeft <= 0.0f)
		{
			if(players[current].getActions().getCurrentAction().isFinished())
				nextTurn();
			else
				overTimeTurn = true;
		}

		playPlayerAction();
	}

	/**
	 * 
	 */
	protected void skipTime() 
	{
		currentTimeLeft = MAX_TURN_TIME - (System.currentTimeMillis()-startSkip);
	}
	
	protected void playPlayerAction() 
	{

		players[current].getActions().playAction();
		
		int next = (current+1)%players.length;
		players[next].update(0);
		
		if(gameStatus && !players[next].hasAliveCharacters())
		{
			
			gameStatus = false;
			postMatchActions(WINNER_EXPERIENCE, players[current].getCharacters());
			postMatchActions(LOSER_EXPERIENCE, players[next].getCharacters());
			EventManager.getInstance().triggerEvent(new EndMatchEvent(players[current].getNickname(), players[next].getNickname()));
		}
		
	}

	/** */
	public boolean isMatchRunning()
	{
		return gameStatus;
	}
	
	/** */
	public Map getMap()
	{
		return map;
	}
	
	/** */
	public void init(SimplePlayer one, SimplePlayer two, BattlePlayer playerOne, BattlePlayer playerTwo, Map arena)
	{
		map = arena;
		players[CLIENT] = playerOne;
		players[ENEMY] = playerTwo;
	
		// Set the actions to the players.
		playerOne.setActions(new ActionMediator(this.getActions(one, playerOne, playerTwo, statsFactory), this.getActionInformations()));
		playerTwo.setActions(new ActionMediator(this.getActions(two, playerTwo, playerOne, statsFactory), this.getActionInformations()));
		players[CLIENT].getActions().setNoAction();
		players[ENEMY].getActions().setNoAction();
	}
	
	/** */
	public boolean check()
	{
		return checkClient() && checkEnemy();
	}
	
	public boolean checkClient()
	{
		return check(CLIENT); 
	}
	
	public boolean checkEnemy()
	{
		return check(ENEMY); 
	}
	
	private boolean check(int index)
	{
		return players[index].getCharacters().size() >= MINIMUM_CHARACTERS_PER_PLAYER && players[index].getCharacters().size() <= MAX_CHARACTERS_PER_PLAYER; 
	}
	
	/** */
	public void startMatch(final int STARTING_PLAYER)
	{
		gameStatus = true;
		
		setMaximumCharacteristics(players[CLIENT]);
		setMaximumCharacteristics(players[ENEMY]);
		
		players[CLIENT].getActions().setNoAction();
		players[ENEMY].getActions().setNoAction();

		current = STARTING_PLAYER;

		// Set the starting gold and, indirectly, the alive characters.
		players[CLIENT].update(STARTING_SKILL_POINTS);
		players[ENEMY].update(STARTING_SKILL_POINTS);

		startSkip = System.currentTimeMillis();

		turnEvent.setOldPlayer(players[(current+1)%2].getNickname());
		turnEvent.setNewPlayer(players[current].getNickname());
		turnEvent.setSuccess(true);
		EventManager.getInstance().triggerEvent(turnEvent);
	}
	
	/** */
	private HashMap<Float, GenericActionInfo> getActionInformations() {
		HashMap<Float, GenericActionInfo> actionInformations = new HashMap<>();
		
		actionInformations.put(ActionCoordinateInfo.GUID, new ActionCoordinateInfo(-1, -1));
		actionInformations.put(ActionSpellInfo.GUID, new ActionSpellInfo(-1));
		actionInformations.put(AddCharacterInfo.GUID, new AddCharacterInfo());
		
		return actionInformations;
	}
	
	/** */
	private HashMap<Float, GenericLogicAction> getActions(SimplePlayer performer, BattlePlayer battlePerformer, BattlePlayer enemy, StatsFactory rsp)
	{
		HashMap<Float, GenericLogicAction>  actions = new HashMap<>();
		Chessboard chessboard = map.getArena();
		
		// Add movement action.
		actions.put(MovementAction.GUID, new MovementAction(performer, chessboard));
		
		// Add character attack action.
		actions.put(CharacterAttackAction.GUID, new CharacterAttackAction(performer, chessboard, battlePerformer, enemy));
		
		// Add ability attack action.
		actions.put(AbilityAttackAction.GUID, new AbilityAttackAction(performer, chessboard, battlePerformer, enemy));
		
		// Add the end/next turn action.
		actions.put(EndTurnAction.GUID, new EndTurnAction(this, battlePerformer));
		
		// Add the character adding action, for selecting the characters who play the match.
		actions.put(SelectCharacterAction.GUID, new SelectCharacterAction(performer.getCharacters(), battlePerformer, rsp, MAX_CHARACTER));
		
		// Add the selecting staring point of character action.
		actions.put(StartingPointAction.GUID, new StartingPointAction(performer, battlePerformer, chessboard));
//		actions.put(StartingPointAction.GUID, new StartingPointAction(, chessboard));

		// Add the removing starting point of character action.
		actions.put(RemoveStartingPointAction.GUID, new RemoveStartingPointAction(performer, battlePerformer, chessboard));
		
		return actions;
	}
	
	/** */
	private void postMatchActions(final int exp, List<Character> fighters)
	{
		ItemFactory itmf = ItemFactory.getInstance();
		
		for (Character character : fighters) 
		{
			character.setExperience(character.getExperience()+exp);
			character.setStats(statsFactory.levellingRole(character.getStats().getName(), character.getLevel()));

			for(String itemName : character.getInventory().getItemNames())
				character.getStats().getCharacteristics().sum(itmf.getItem(itemName), true);
		}
		
	}
	
	/** */
	public void disband()
	{
		
		if(players[CLIENT] != null)
		{
			players[CLIENT].disband();
		}
		
		if(players[ENEMY] != null)
		{
			players[ENEMY].disband();
		}
	}

	/**
	 * @return the currentTimeLeft
	 */
	public float getCurrentTimeLeft() 
	{
		return currentTimeLeft;
	}
	
	/** */
	private void setMaximumCharacteristics(final BattlePlayer player)
	{
		List<Character> characters = player.getCharacters();
		
		for (Character character : characters) 
		{
			character.getStats().setMaxCharacteristics(new Stats(character.getStats().getCharacteristics()));
		}
		
	}

	/**
	 * @return the overTimeTurn
	 */
	public boolean isOverTimeTurn() {
		return overTimeTurn;
	}

	/**
	 * @param overTimeTurn the overTimeTurn to set
	 */
	public void setOverTimeTurn(boolean overTimeTurn) {
		this.overTimeTurn = overTimeTurn;
	}
	
	/** */
	public boolean noTimeLeft()
	{
		return currentTimeLeft <= 0;
	}

	/**
	 * @param map the map to set
	 */
	public void setMap(Map map) {
		this.map = map;
	}
}
